<?php
/**
 * CRequest class file.
 * URL请求模式处理类.
 * @author Huan Song <iphper@yeah.net>
 * @version $Id: CRequest.php 1 2011-02-24 09:24:06Z huan.song $
 * @package system.controller
 * @since 2.0
 */
class CRequest extends CApplication
{
	/**
     * Scheme for http.
     */
    const SCHEME_HTTP  = 'http';
	
    /**
     * Scheme for https.
     */
    const SCHEME_HTTPS = 'https';
	
	/**
	 * @var string 网站根路径.
	 */
    protected $_baseUrl;
    
    /**
	 * @var string $_SERVER['REQUEST_URI'].
	 */
    protected $_requestUri;
    
    /**
	 * @var string $_SERVER['PATH_INFO'].
	 */
    protected $_pathInfo;
    
    /**
	 * @var string $_SERVER['REMOTE_ADDR'].
	 */
    protected $_clientIp;
    
    /**
	 * @var string URL根目录.
	 */
    protected $_basePath;
    
    /**
	 * @var string 根目录绝对路径.
	 */
    protected $_absoluteUrl;
    
    /**
	 * @var object CRequest.
	 */
	protected static $_instance;
	
	/**
	 * 构造方法: 禁止被实例化.
	 */
	protected function __construct()
    {
    	$this->_initGET();
    }
    
    /**
	 * 魔术方法: 禁止被克隆.
	 */
    protected function __clone() 
    {
    }
	
    /**
	 * 单例模式: 获取本类的实例化对象.
	 */
	public static function getInstance()
    {
        if (null === self::$_instance) 
            self::$_instance = new self();
		
        return self::$_instance;
    }
    
    /**
	 * 如果URL为PATHINFO或者REWRITE模式, 解析URL为$_GET赋值.
	 * 重写模式, 必须开启mod_rewrite, 根目录下必须有.htaccess文件.
	 * @return boolean.
	 */
    protected function _initGET()
    {
    	if (CControllerConfig::isNormal())
    	{
    		if (null === ($r = $this->getParam('r')))
    			return false;
    	}
    	else
    		$r = trim($this->getPathInfo());
    	
    	$r = trim($r, '/');
    	if (empty($r))
    		return false;
    	
    	$bits = explode('/', $r);
    	
		if (isset($bits[0]))
		{
			$_GET['c'] = array_shift($bits);
			if (isset($bits[0]))
				$_GET['a'] = array_shift($bits);
		}
		
		if (!CControllerConfig::isNormal())
		{
			$last = count($bits);
			if ($last > 0)
			{
				for ($i = 0; $i < $last - 1; $i += 2)
				{
					$_GET[$bits[$i]] = $bits[$i+1];
					$_GET[] = $bits[$i+1];
				}
			}
			
			if ($last % 2 !== 0)
				$_GET[] = array_pop($bits);
		}
		
		return true;
    }
    
	/**
	 * 通过键从$_GET中获取数据. 如果$_GET中键不存在, 再从$_POST中获取.
	 * @param string $name.
	 * @param array|string|integer|null $defaultValue.
	 * @return array|string|integer|.etc.
	 * @see getQuery
	 * @see getPost
	 */
	public function getParam($name, $defaultValue = null)
	{
		return isset($_GET[$name]) ? $_GET[$name] : (isset($_POST[$name]) ? $_POST[$name] : $defaultValue);
	}
	
	/**
	 * 通过键从$_GET中获取数据.
	 * @param string $name.
	 * @param array|string|integer|null $defaultValue.
	 * @return array|string|integer|.etc.
	 * @see getParam
	 * @see getPost
	 */
	public function getQuery($name, $defaultValue = null)
	{
		return isset($_GET[$name]) ? $_GET[$name] : $defaultValue;
	}
	
	/**
	 * 通过键从$_POST中获取数据.
	 * @param string $name.
	 * @param array|string|integer|null $defaultValue.
	 * @return array|string|integer|.etc.
	 * @see getParam
	 * @see getQuery
	 */
	public function getPost($name, $defaultValue = null)
	{
		return isset($_POST[$name]) ? $_POST[$name] : $defaultValue;
	}
	
	/**
     * 通过NORMAL(1), PATHINFO(2), REWRITE(3)三种方式创建URL.
     * @param array|null $params URL的参数.
     * @param string|null $action 跳转到的方法. 如果方法为null, 则取本控制器方法.
     * @param string|null $controller 跳转到的控制器. 如果控制器为null, 则取本控制器.
     * @param string $url URL地址. 如果不是一个字符串, 则取baseUrl
     * @return string 拼接好的URL地址.
     */
    public function createUrl($params = array(), $action = null, $controller = null, $url = null)
    {
    	if (null === $action)
			$action = CControllerConfig::getAction();
			
		if (null === $controller)
			$controller = CControllerConfig::getController();
			
		if (null === $url)
			$url = $this->getBaseUrl();
		
		$url .= CControllerConfig::isNormal() ? '?r=' : '/';
		
		if(!empty($controller)) 
			$url .= $controller;
			
		if (!empty($action))
			$url .= '/' . $action;
		
		foreach ($params as $key => $param)
		{
			if (CControllerConfig::isNormal())
				$url .= '&' . $key . '=' . urlencode($param);
			else
				$url .= '/' . $key . '/' . urlencode($param);
		}
		
		return $url;
    }
    
	/**
	 * 获取REQUEST_URI.
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $requestUri = $CRequest->getRequestUri();
	 * $requestUri = $CRequest->requestUri;
	 * REWRITE模式结果: '/C/A/username/iphper/email/iphper@yeah.net'
	 * PATHINFO模式结果: '/index.php/C/A/username/iphper/email/iphper@yeah.net'
	 * NORMAL模式结果: '/index.php?r=C/A&username=iphper&email=iphper@yeah.net'
	 * </pre>
     * @return string REQUEST_URI.
     * @see setRequestUri
     */
    public function getRequestUri()
    {
    	if (null === $this->_requestUri)
    		$this->setRequestUri();
    	
    	return $this->_requestUri;
    }
    
    /**
     * 设置REQUEST_URI.
     * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $requestUri = null;
	 * $CRequest->setRequestUri($requestUri);
	 * $CRequest->requestUri = $requestUri;
	 * </pre>
     * @param string|null $requestUri. 如果为空, 从$_SERVER中获取.
     * @return Object CRequest.
     * @see getRequestUri
     */
	public function setRequestUri($requestUri = null)
	{
		if (null === $requestUri) 
		{
			// check this first so IIS will catch
			if (isset($_SERVER['HTTP_X_REWRITE_URL'])) 
				$requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
			// IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem)
			else if (isset($_SERVER['IIS_WasUrlRewritten']) && $_SERVER['IIS_WasUrlRewritten'] == '1'
				 	 && isset($_SERVER['UNENCODED_URL']) && $_SERVER['UNENCODED_URL'] != '') 
				$requestUri = $_SERVER['UNENCODED_URL'];
			else if (isset($_SERVER['REQUEST_URI'])) 
			{
				$requestUri = $_SERVER['REQUEST_URI'];
				// Http proxy reqs setup request uri with scheme and host [and port] + the url path, only use url path
				$schemeAndHttpHost = $this->getScheme() . '://' . $this->getHttpHost();
				if (strpos($requestUri, $schemeAndHttpHost) === 0) 
					$requestUri = substr($requestUri, strlen($schemeAndHttpHost));
			} 
			// IIS 5.0, PHP as CGI
			else if (isset($_SERVER['ORIG_PATH_INFO'])) 
			{ 
				$requestUri = $_SERVER['ORIG_PATH_INFO'];
				if (!empty($_SERVER['QUERY_STRING'])) 
					$requestUri .= '?' . $_SERVER['QUERY_STRING'];
			} 
			else
				return $this;
		} 
		else if (!is_string($requestUri)) 
			return $this;
		
		$this->_requestUri = $requestUri;
		return $this;
    }
    
	/**
	 * 获取网站根路径.
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $baseUrl = $CRequest->getBaseUrl();
	 * $baseUrl = $CRequest->baseUrl;
	 * REWRITE模式结果: ''
	 * PATHINFO模式结果: '/index.php'
	 * header('location: ' . $baseUrl . '/C/A/queryString');
	 * 
	 * NORMAL模式结果: '/index.php'
	 * header('location: ' . $baseUrl . '?r=C/A&queryString');
	 * </pre>
     * @return string.
     * @see setBaseUrl
     */
    public function getBaseUrl()
    {
    	if (null === $this->_baseUrl)
    		$this->setBaseUrl();
    	
    	return $this->_baseUrl;
    }
    
    /**
     * 设置网站根路径.
     * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $baseUrl = null;
	 * $CRequest->setBaseUrl($baseUrl);
	 * $CRequest->baseUrl = $baseUrl;
	 * </pre>
     * @param string|null $baseUrl. 如果为空, 从$_SERVER中获取.
     * @return Object CRequest.
     * @see getBaseUrl
     */
	public function setBaseUrl($baseUrl = null)
    {
		if ((null !== $baseUrl) && !is_string($baseUrl)) 
			return $this;

		if (null === $baseUrl) 
		{
			$filename = (isset($_SERVER['SCRIPT_FILENAME'])) ? basename($_SERVER['SCRIPT_FILENAME']) : '';
			
			if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) 
				$baseUrl = $_SERVER['SCRIPT_NAME'];
			else if (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $filename)
				$baseUrl = $_SERVER['PHP_SELF'];
			// 1and1 shared hosting compatibility
			else if (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename)
				$baseUrl = $_SERVER['ORIG_SCRIPT_NAME']; 
			else 
			{
				// Backtrack up the script_filename to find the portion matching
				// php_self
				$path    = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '';
				$file    = isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '';
				$segs    = explode('/', trim($file, '/'));
				$segs    = array_reverse($segs);
				$index   = 0;
				$last    = count($segs);
				$baseUrl = '';
				do 
				{
					$seg     = $segs[$index];
					$baseUrl = '/' . $seg . $baseUrl;
					++$index;
				} 
				while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos));
			}
			
			// Does the baseUrl have anything in common with the request_uri?
			$requestUri = $this->getRequestUri();
			
			if (0 === strpos($requestUri, $baseUrl)) 
			{
				// full $baseUrl matches
				$this->_baseUrl = $baseUrl;
				return $this;
			}
			
			if (0 === strpos($requestUri, dirname($baseUrl))) 
			{
				// directory portion of $baseUrl matches
				$this->_baseUrl = rtrim(dirname($baseUrl), '/');
				return $this;
			}
			
			$basename = basename($baseUrl);
			if (empty($basename) || !strpos($requestUri, $basename)) 
			{
				// no match whatsoever; set it blank
				$this->_baseUrl = '';
				return $this;
			}
			
			// If using mod_rewrite or ISAPI_Rewrite strip the script filename
			// out of baseUrl. $pos !== 0 makes sure it is not matching a value
			// from PATH_INFO or QUERY_STRING
			if ((strlen($requestUri) >= strlen($baseUrl)) && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0)))
				$baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
		}

		$this->_baseUrl = rtrim($baseUrl, '/');
		return $this;
    }
    
	/**
	 * 获取PATH_INFO.
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $baseUrl = $CRequest->getPathInfo();
	 * $baseUrl = $CRequest->pathInfo;
	 * REWRITE模式结果: '/C/A/username/iphper/email/iphper@yeah.net'
	 * PATHINFO模式结果: '/C/A/username/iphper/email/iphper@yeah.net'
	 * NORMAL模式结果: ''
	 * </pre>
     * @return string PATH_INFO.
     * @see setPathInfo
     */
    public function getPathInfo()
    {
    	if (null === $this->_pathInfo)
    		$this->setPathInfo();
    	
    	return $this->_pathInfo;
    }
    
    /**
     * 设置PATH_INFO.
     * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $pathInfo = null;
	 * $CRequest->setPathInfo($pathInfo);
	 * $CRequest->pathInfo = $pathInfo;
	 * </pre>
     * @param string|null $pathInfo. 如果为null, 从Url中获取PathInfo.
     * @return Object CRequest.
     * @see getPathInfo
     */
	public function setPathInfo($pathInfo = null)
	{
		if (null === $pathInfo) 
        {
			$baseUrl = $this->getBaseUrl();
			
			if (null === ($requestUri = $this->getRequestUri())) 
				return $this;
			
			// 去掉REQUEST_URI问号后面的串
			if ($pos = strpos($requestUri, '?')) 
				$requestUri = substr($requestUri, 0, $pos);
			
			// 如果截得串为false, $pathInfo为空字串
			if ((null !== $baseUrl) && (false === ($pathInfo = substr($requestUri, strlen($baseUrl)))))
				$pathInfo = ''; 
			elseif (null === $baseUrl) 
				$pathInfo = $requestUri;
		}

		$this->_pathInfo = (string) $pathInfo;
		return $this;
	}
	
	/**
	 * 获取HTTPS or HTTP.
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $scheme = $CRequest->getScheme();
	 * $scheme = $CRequest->scheme;
	 * </pre>
     * @return string https or http.
     */
	public function getScheme()
	{
		return ($this->getServer('HTTPS') == 'on') ? self::SCHEME_HTTPS : self::SCHEME_HTTP;
	}
	
	/**
	 * 获取Host.
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $httpHost = $CRequest->getHttpHost();
	 * $httpHost = $CRequest->httpHost;
	 * 结果: www.website.com
	 * www.website.com:8080
	 * </pre>
     * @return string Host.
     */
	public function getHttpHost()
    {
        $host = $this->getServer('HTTP_HOST');
        if (!empty($host))
            return $host;
		
        $scheme = $this->getScheme();
        $name   = $this->getServer('SERVER_NAME');
        $port   = $this->getServer('SERVER_PORT');

        if (($scheme == self::SCHEME_HTTP && $port == 80) || ($scheme == self::SCHEME_HTTPS && $port == 443)) 
            return $name;
        else 
            return $name . ':' . $port;
    }
	
    /**
     * 从$_SERVER中取值.
     * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $server = $CRequest->getServer();
	 * 结果: $_SERVER
	 * 
	 * $server = $CRequest->getServer('REMOTE_ADDR');
	 * 结果: $_SERVER['REMOTE_ADDR']
	 * 
	 * $server = $CRequest->getServer('UNDEFINED', $default);
	 * 结果: $default
	 * </pre>
     * @param string|null $key. 如果为null, 返回全部$_SERVER
     * @param string $default.
     * @return string $_SERVER中键位$key的值.
     */
    public function getServer($key = null, $default = null)
    {
        if (null === $key) 
            return $_SERVER;
		
        return (isset($_SERVER[$key])) ? $_SERVER[$key] : $default;
    }
    
    /**
     * 获取客户端IP地址.
     * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $clientIp = $CRequest->getClientIp();
	 * $clientIp = $CRequest->clientIp;
	 * </pre>
	 * @return string 客户端IP地址.
	 * @see _setClientIp
	 */
    public function getClientIp()
    {
    	if (null === $this->_clientIp)
    		$this->_setClientIp();
    	
    	return $this->_clientIp;
    }
    
	/**
     * 设置客户端IP地址.
     * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $CRequest->_setClientIp();
	 * </pre>
     * @return Object CRequest.
     * @see getClientIp
     */
	protected function _setClientIp() 
	{
		$direct_ip = '';
		// Gets the default ip sent by the user
		if (!empty($_SERVER['REMOTE_ADDR'])) 
			$direct_ip = $_SERVER['REMOTE_ADDR'];
		
		// Gets the proxy ip sent by the user
		$proxy_ip = '';
		if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) 
			$proxy_ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
		else if (!empty($_SERVER['HTTP_X_FORWARDED'])) 
			$proxy_ip = $_SERVER['HTTP_X_FORWARDED'];
		else if (!empty($_SERVER['HTTP_FORWARDED_FOR'])) 
			$proxy_ip = $_SERVER['HTTP_FORWARDED_FOR'];
		else if (!empty($_SERVER['HTTP_FORWARDED'])) 
			$proxy_ip = $_SERVER['HTTP_FORWARDED'];
		else if (!empty($_SERVER['HTTP_VIA'])) 
			$proxy_ip = $_SERVER['HTTP_VIA'];
		else if (!empty($_SERVER['HTTP_X_COMING_FROM'])) 
			$proxy_ip = $_SERVER['HTTP_X_COMING_FROM'];
		else if (!empty($_SERVER['HTTP_COMING_FROM'])) 
			$proxy_ip = $_SERVER['HTTP_COMING_FROM'];
			
		// Returns the true IP if it has been found, else false
		if (empty($proxy_ip)) 
			// True IP without proxy
			$this->_clientIp = $direct_ip;
		else 
		{
			$is_ip = preg_match('|^([0-9]{1,3}\.){3,3}[0-9]{1,3}|', $proxy_ip, $regs);
			if ($is_ip && (count($regs) > 0)) 
				// True IP behind a proxy
				$this->_clientIp = $regs[0];
			else 
				// Can't define IP: there is a proxy but we don't have
				// information about the true IP
				$this->_clientIp = $direct_ip;
		}
		
		return $this;
	}
	
	/**
	 * 获取当前页面链接地址.
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $url = $CRequest->getUrl();
	 * $url = $CRequest->url;
	 * </pre>
	 * @return string 当前页面链接地址.
	 * @see getRequestUri
	 */
	public function getUrl()
	{
		return $this->getRequestUri();
	}
	
	/**
     * 获取URL根目录.
     * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $basePath = $CRequest->getBasePath();
	 * $basePath = $CRequest->basePath;
	 * REWRITE模式结果: ''
	 * PATHINFO模式结果: ''
	 * NORMAL模式结果: ''
	 * <img src="<?php echo $basePath; ?>/public/images/vivo.png"/>.
	 * </pre>
     * @return string.
     * @see setBasePath
     */
	public function getBasePath()
	{
		if (null === $this->_basePath)
			$this->setBasePath();
		
		return $this->_basePath;
	}
	
	/**
	 * 设置URL根目录名.
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $basePath = null;
	 * $CRequest->setBasePath($basePath);
	 * $CRequest->basePath = $basePath;
	 * </pre>
	 * @param string|null $basePath. 如果为null, 从BaseUrl中获取.
	 * @return Object CRequest.
	 * @see getBasePath
	 */
	public function setBasePath($basePath = null)
	{
		if (null === $basePath) {
			$filename = (isset($_SERVER['SCRIPT_FILENAME']))
			? basename($_SERVER['SCRIPT_FILENAME'])
			: '';
			
			$baseUrl = $this->getBaseUrl();
			if (empty($baseUrl)) 
			{
				$this->_basePath = '';
				return $this;
			}
			
			if (basename($baseUrl) === $filename)
				$basePath = dirname($baseUrl);
			else 
				$basePath = $baseUrl;
		}
		
		if (substr(PHP_OS, 0, 3) === 'WIN') 
			$basePath = str_replace('\\', '/', $basePath);
		
		$this->_basePath = rtrim($basePath, '/');
		return $this;
	}
	
	/**
	 * 获取网站根绝对路径.
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $absoluteBaseUrl = $CRequest->getAbsoluteBaseUrl();
	 * $absoluteBaseUrl = $CRequest->absoluteBaseUrl;
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $absoluteBaseUrl = $CRequest->getAbsoluteBaseUrl();
	 * $absoluteBaseUrl = $CRequest->absoluteBaseUrl;
	 * REWRITE模式结果: 'http://www.website.com'
	 * PATHINFO模式结果: 'http://www.website.com/index.php'
	 * header('location: ' . $baseUrl . '/C/A/queryString');
	 * 
	 * NORMAL模式结果: 'http://www.website.com/index.php'
	 * header('location: ' . $baseUrl . '?r=C/A&queryString');
	 * </pre>
	 * @return string.
	 */
	public function getAbsoluteBaseUrl()
	{
		return $this->getAbsoluteUrl() . $this->getBaseUrl();
	}
	
	/**
	 * 获取网站域名.
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $absoluteUrl = $CRequest->getAbsoluteUrl();
	 * $absoluteUrl = $CRequest->absoluteUrl;
	 * REWRITE模式结果: 'http://www.website.com'
	 * PATHINFO模式结果: 'http://www.website.com'
	 * NORMAL模式结果: 'http://www.website.com'
	 * </pre>
	 * @return string 网站域名.
	 * @see _setAbsoluteUrl
	 */
	public function getAbsoluteUrl()
	{
		if (null === $this->_absoluteUrl)
			$this->_setAbsoluteUrl();
		
		return $this->_absoluteUrl;
	}
	
	/**
	 * 设置网站域名.
	 * <pre>
	 * $CRequest = CRequest::getInstance();
	 * $CRequest->_setAbsoluteUrl();
	 * </pre>
	 * @param string|null $absoluteUrl. 如果为null, 从HTTP_HOST, HTTPS, SERVER_PORT拼接.
	 * @return Object CRequest.
	 * @see getAbsoluteUrl
	 */
	protected function _setAbsoluteUrl()
	{
		$host  = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
		$proto = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
		$port  = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
		$this->_absoluteUrl = $proto . '://' . $host;
		if ((('http' == $proto) && (80 != $port)) || (('https' == $proto) && (443 != $port)))
			$this->_absoluteUrl .= ':' . $port;
			
		return $this;
	}
}